/*
 * @Date: 2023-12-19 15:44:02
 * @LastEditors: lyx
 * @LastEditTime: 2024-03-20 18:04:18
 * @FilePath: \taro-template3\src\pages\index\hooks\store.tsx
 * @Description:
 */
import { Toast } from "@nutui/nutui-react-taro";
import Taro from "@tarojs/taro";
import React, { useState, createContext, useContext, useRef, useEffect } from "react";
import { keyBy, remove, findIndex, last } from "lodash";
import { requirePlugin } from "@tarojs/taro";
import BaseOverlay from "@/components/BaseOverlay";
import * as Notify from "@/components/BaseNotify";
import { IQItem } from "./type";
import { CHAT_ACTION, CHAT_EVENT, CHAT_ROLE, DEFAULT_ERROR_ITEM, DEFAULT_Q_LIST, DEFAULT_RECOMMENDED_NUMBER, DEVICE_TYPE, IChatHistoryItem, SESSION_TYPE, addChatHistory, clearAllChat, clearChatContext, getChatItemContent, getDefaultQuestionList, handleChunkResp, selectChatHistoryItem, setChatHistory, updateHistoryById } from "@/utils/chat";
import { generateUuid } from "@/utils/common";
import { REQ_FAIL_ERROR_MSG, chat_baseUrl } from "@/utils/request";
import { IToFeedBackParams, fetchAddSession, fetchDelSession, fetchMessages, fetchSessions, getRecommended, handleFeedback } from "../services/index";
import { useDeepCompareEffect } from "ahooks";

const plugin = requirePlugin("WechatSI");
const dayjs = require("dayjs");
interface IChatContextProps {
  isLoading: boolean;
  qList: Array<IQItem>;
  value: string;
  viewContent: Partial<IQItem>;
  talkLists: Array<IChatHistoryItem>;
  selectTalk: any;
  overlayVisible: boolean;
  setIsLoading: (loading: boolean) => void;
  setQList: (list: any[]) => void;
  setValue: (str: string) => void;
  handle: (v?: string, addReqObj?: IAddReqObj) => void;
  reLoad: (item: IQItem, fast?: boolean) => void;
  toPlay: (item: IQItem) => void;
  cancelAsk: () => void;
  toFeedback: (info: IToFeedBackParams) => void;
  setViewContent: (content: Partial<IQItem>) => void;
  createGroup: () => Promise<void>;
  onHandleChangeTalk: (type: string, selectTalk: IChatHistoryItem, cb?: (info?: any) => void) => void;
  updateTask: (task: any) => void;
}

interface IProps {
  children: JSX.Element;
}
interface IAddReqObj {
  /**
   * @add 是否加入数组? 重新提问
   */
  add?: boolean;
  /**
   * @list 当前数组
   */
  list?: Array<IQItem>;
  /**
   * @action 操作逻辑
   */
  action?: CHAT_ACTION;
  /**
   * @msgId  msgId
   */
  msgId?: string;
  /**
   * @parentMsgId 前置 parentMsgId
   */
  parentMsgId?: string;
}

interface IChatSourceMap {
  id?: IQItem
}

export const ChatContext = createContext<IChatContextProps>(
  {} as IChatContextProps
);

const audio = Taro.createInnerAudioContext();

export const useChatContextProvider = (props: IIndexContext): IChatContextProps => {
  // const { showBack, scrollToBottom } = useContext(IndexContext);
  const { showBack, scrollToBottom } = props;
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [overlayVisible, setOverlayVisible] = useState<boolean>(false);
  const [qList, setQList] = useState<Array<IQItem>>(DEFAULT_Q_LIST);

  // 首次进入是否加载当前会话的历史对话
  const [isInitLoadSession, setIsInitLoadSession] = useState<boolean>(true);
  // 对话历史: 缓存到 storage
  const [talkLists, setTalkLists] = useState<any>([]);
  // 当前对话上下文: 缓存到 storage=> 如果没有当前对话,则在提问的时候第一个问题会赋值
  const [selectTalk, setSelectTalk] = useState<{ id?: string, content?: string }>({});

  // ! 输入的问题
  const [value, setValue] = useState<string>("");

  // ! 当前正在生成的回答
  const [viewContent, setViewContent] = useState<Partial<IQItem>>({});

  // 提问: 回答 缓存对象
  const [chatSourceMap, setChatSourceMap] = useState<IChatSourceMap>({});

  // 缓存当前再chunk 的对话
  const chunkCacheInfoRef = useRef<any>({});
  // 当前对话对象
  const requestTaskRef = useRef<any>(null);
  const audioRef = useRef<any>(null);
  // const audio = Taro.createInnerAudioContext();
  audio.autoplay = true;
  audio.obeyMuteSwitch = false;
  audio.src = '';
  audio.volume = 1;

  audio.onPlay(() => {
    setOverlayVisible(false);
    console.log("开始播放");
  });
  audio.onPause(() => {
    console.log("暂停播放");
  });
  audio.onStop(() => {
    console.log("停止");
  });

  audioRef.current = audio;

  const loadingActionFb = () => {
    Notify.show({
      type: 'warning',
      msg: '小蜜正在处理问题中哦~',
      duration: 3000,
    })
    return
  }

  const delSession = async (id, cb) => {
    setIsLoading(true);
    const res = await fetchDelSession(id);
    setIsLoading(false);

    const { success, msg } = res;
    if (success) {
      let cList = [...talkLists];
      let delList = remove(cList, function (n) {
        return n.id === id;
      }) ?? [];
      let cSessionItem = cList?.[0] ?? {};

      setSelectTalk(cSessionItem);
      selectChatHistoryItem(cSessionItem);
      setChatHistory(cList);
      cb?.();

      if (!cSessionItem.id) {
        // 没有历史记录,创建新的空对话
        createGroup();
      }
    } else {
      return Toast.show("chat-toast", { title: msg });
    }
  }

  /**
   * @description: 获取指定会话的聊天记录
   * @param {*} id sessionId
   * @param {*} initLoad 是否是首次加载
   * @return {*}
   */
  const getSessionMessages = async (id, initLoad = false) => {
    setIsLoading(true);
    const res = await fetchMessages(id);
    setIsLoading(false);
    const { success, msg, content = [] } = res;
    if (success) {
      setQList(content);
      let currentChatSourceMap = keyBy(content, 'msgId');
      setChatSourceMap(currentChatSourceMap);
      if (initLoad) {
        setIsInitLoadSession(false)
      }
      updateScrollTop(true);
    } else {
      Toast.show("chat-toast", { title: msg })
    }
  }

  const changeSession = async (sessionTalk, cb) => {
    await getSessionMessages(sessionTalk.id)
    setSelectTalk(sessionTalk);
    selectChatHistoryItem(sessionTalk);
    cb?.();
  }

  const onHandleChangeTalk = (type, selectTalk, cb) => {
    if (type === "add") {
      createGroup();
    } else if (type === "select") {
      changeSession(selectTalk, cb);
    } else if (type === "del") {
      delSession(selectTalk.id, cb);
    }
  };

  /**
   * @description: 新建会话
   * @return {*} sessionId
   */
  const addSession = async () => {
    if (!selectTalk.id) {
      // 当前没有会话
      const sessionRes = await fetchAddSession({});
      const { success, msg, content } = sessionRes;
      if (success) {
        const { id } = content;
        let sessionItem = {
          id,
          content: '',
        };
        setSelectTalk(sessionItem);
        selectChatHistoryItem(sessionItem);
        return id;
      } else {
        Notify.show({
          type: 'danger',
          msg: msg,
          duration: 3000,
        });
        return false
      }
    }
    // 有 会话直接跳过
    return selectTalk.id
  }

  /**
   * @description: 更新对话 数组
   * @param {string} id 项 id
   * @param {Partial} content 更新 内容
   * @return {*}
   */
  const updateChatSome = (id: string, content: Partial<IQItem>, list: Array<IQItem> | null = null) => {
    console.log('updateChatSome id', id, content);
    let cChatList = list || [...qList];

    let updateIndex = findIndex(cChatList, (item) => {
      return item.id === id
    });

    // 如果找到了项，更新它的age属性
    if (updateIndex > -1) {
      let cData = cChatList[updateIndex];
      cData = {
        ...cData,
        ...content
      };
      cChatList[updateIndex] = {
        ...cData
      }
    }
    console.log('cChatList', cChatList);
    setQList([...cChatList])
  }

  /**
   * @description: 当前所有对话项 都不播放语音
   * @return {*}
   */
  const batchUpdateChatSomeSpeakingFalse = () => {
    let cChatList = [...qList].map((item) => {
      return {
        ...item,
        speaking: false
      }
    });
    return cChatList;
  }

  /**
   * @description: 停止生成 
   * @return {*}
   */
  const cancelAsk = async () => {
    Notify.show({
      type: 'success',
      msg: 'TODO 停止生成',
      duration: 3000,
      onClose: () => {
        console.log('这是我的onClose');
      }
    })
    setIsLoading(false);
    requestTaskRef?.current?.abort?.();
    setViewContent((prev) => {
      return {
        ...prev,
        cancelAsk: true
      }
    });
  }

  const updateScrollTop = (ignoreShowBack = false) => {
    console.log("updateScrollTop");
    if (ignoreShowBack) {
      scrollToBottom?.();
    } else {
      if (!showBack) {
        // 在当前回答则自动滚动到底部, 不然就是在看其他内容
        scrollToBottom?.();
      }
    }

  };

  /**
   * @description: 把回答信息加入展示队列
   * @param {IQItem} data 当前回答内容
   * @param {Array} cList 当前已经存在的队列
   * @return {*}
   */
  const addToReqList = (data: IQItem, cList: Array<IQItem> = []) => {
    console.log('addToReqList cList', cList);
    const viewIndex = cList.findIndex(i => i.role === CHAT_ROLE.view)
    console.log('viewIndex', viewIndex);
    if (viewIndex !== -1) {
      cList[viewIndex] = data;
    } else {
      cList.push({ ...data });
    }

    setQList([...cList]);
    updateScrollTop();
  };

  /**
   * @description: 提问
   * @param {*} voiceTvalue 语音输入内容
   * @param {*} addReqObj 提问额外参数
   * @return {*}
   */
  const handle = async (voiceTvalue = "", addReqObj: IAddReqObj = {}) => {
    const cValue = voiceTvalue || value;
    const { add = true, parentMsgId = '', msgId: pMsgId = '', action = CHAT_ACTION.next, list = [] } = addReqObj;
    console.log('handle addReqObj', addReqObj);
    if (isLoading) {
      return Toast.show("chat-toast", { title: "小蜜正在处理上一个问题中哦~" });
    }
    if (!cValue) {
      return Toast.show("chat-toast", { title: "请客官输入内容哦~" });
    }
    setViewContent({});

    setIsLoading(true);
    const reqValue = cValue;
    setValue("");

    console.log('sessionRes 111');
    const sessionId = await addSession();
    console.log('sessionRes 222', sessionId);

    if (!sessionId) {
      return
    }

    // stopAllVoicePlay();

    let cList: any = add ? [...qList] : [...list];
    const respList = cList.filter((item) => item.role === CHAT_ROLE.view)
    const msgId = generateUuid();
    const options: IQItem = last(respList) ?? {} as IQItem;
    const pParentMsgId = parentMsgId || options.parentMsgId || '0';

    const askInfo = {
      id: msgId,
      createTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
      msgId: msgId,
      childMsgId: [],
      messageBody: {
        contents: [
          {
            role: CHAT_ROLE.user,
            contentType: 'text',
            content: reqValue
          }
        ]
      },
      parentMsgId: pParentMsgId,
      sessionId: sessionId
    }
    if (add) {
      cList.push(askInfo);
      setQList([...cList]);
    }

    let token = Taro.getStorageSync("token");

    // 基础库为2.33.0
    const requestTask = Taro.request({
      url: `${chat_baseUrl}/api/conversation`, // 需要请求的接口地址
      enableChunked: true, // enableChunked必须为true
      method: 'POST',
      header: {
        "Authorization": `Bearer ${token}`
        // "Authorization": "Trust admin"
      },
      data: {
        "data": {
          "action": action,
          "contents": [
            { "content": reqValue, "contentType": "text", "role": CHAT_ROLE.user }
          ],
          msgId: pMsgId || msgId,
          "deviceType": DEVICE_TYPE.app,
          parentMsgId: pParentMsgId,
          "sessionId": sessionId,
          "sessionType": SESSION_TYPE.text_chat,
          "pluginIds": []
        }
      },

      success: (response) => {
        console.log('requestTask success', response);
        const data = chunkCacheInfoRef.current;
        console.log('ch', data);
        addHistoryGroup(reqValue, sessionId);

        // 这里是成功调用完的 可以写去掉加载中等逻辑
        // 由于SSE特性，需要由用户端断开连接，所以在使用完毕时，需要调用requestTask.abort()断开连接
        requestTask.abort();
        setIsLoading(false);
        // TODO: 这里需要判断是否成功? 比如我不能回答你的问题
        addToReqList(data, cList);
      },
      fail: (error) => {
        setIsLoading(false);
        requestTask?.abort?.();
        console.log('fail error', error);
        const { errMsg = '' } = error;
        if (errMsg === REQ_FAIL_ERROR_MSG.abort) {
          return
        }
        Taro.showToast({
          // title: error.errMsg == REQ_FAIL_ERROR_MSG.timeout ? '请求超时，请稍后重试' : '网络错误，请稍后重试',
          title: error.errMsg == REQ_FAIL_ERROR_MSG.timeout ? '请求超时，请稍后重试' : errMsg,
          icon: 'none',
          duration: 1000
        })
      }
    });
    requestTaskRef.current = requestTask;


    // 监听服务端返回的数据
    requestTask.onChunkReceived((response: any) => {
      console.log('onChunkReceived response', response);
      let [event, resData] = handleChunkResp(response);
      console.log('onChunkReceived resData', event, resData);
      if (event === CHAT_EVENT.ERROR) {
        // 报错则显示当前错误信息并且加入展示列表
        let cData = {
          ...DEFAULT_ERROR_ITEM,
          ...chunkCacheInfoRef.current,
        }
        cData['messageBody']['contents'][0]['content'] = resData?.data ?? '';
        resData = cData;
      }
      chunkCacheInfoRef.current = resData;
      const viewIndex = cList.findIndex(i => i.role === CHAT_ROLE.view)
      console.log('viewIndex', viewIndex);
      if (viewIndex !== -1) {
        cList[viewIndex] = { role: CHAT_ROLE.view, ...resData };
      } else {
        // 生成中的回答暂时先加入数组
        console.log('生成中的回答暂时先加入数组', resData);
        setViewContent({ ...resData });
        // 更新问答缓存对象
        let currentChatSourceMap = { ...chatSourceMap };
        currentChatSourceMap[resData.msgId] = {
          ...askInfo,
          msgId: resData.msgId,
          parentMsgId: pParentMsgId
        };
        setChatSourceMap(currentChatSourceMap);
        cList.push({ role: CHAT_ROLE.view, ...resData });
      }
      setQList([...cList]);
    });
    // 移除监听 需传入与监听时同一个的函数对象
    // requestTask.offChunkReceived(listener)
  };

  /**
   * @description: 重新加载回答
   * @param {*} fast ：是否快速加载
   * true: 快速加载: 直接删除最后一个回答 并且重新请求（数组不添加提问）
   * false: 正常加载 走重新提问流程
   * @return {*}
   */
  const reLoad = (contentItem: IQItem, fast = false) => {
    console.log("reLoad ", contentItem, fast);
    console.log("reLoad chatSourceMap", chatSourceMap);
    if (isLoading) {
      return loadingActionFb();
    }
    // 获取这个回答的  问题信息
    const reqInfo = chatSourceMap[contentItem?.parentMsgId] ?? {};
    const reqContent = reqInfo?.messageBody?.contents?.[0] ?? {};
    const parentMsgId = reqInfo?.parentMsgId ?? '';
    const msgId = reqInfo?.msgId ?? '';
    console.log("reLoad reqContent", reqContent);
    // return
    if (fast) {
      // ! 不会走这个分支 直接走else 加入新对话
      const cList = [...qList];
      const lastContent = cList.pop();
      console.log("lastContent", lastContent);
      setQList(cList);
      handle(reqContent?.content, {
        add: false,
        list: cList,
        parentMsgId: parentMsgId,
        msgId: msgId,
        action: CHAT_ACTION.variant
      });
    } else {
      handle(reqContent?.content, {
        parentMsgId: parentMsgId,
        msgId: msgId,
        action: CHAT_ACTION.variant
      });
    }
  };

  /**
   * @description: 语音播报
   * @param {string} url 文件地址
   * @return {*}
   */
  const speech = (url: string) => {
    console.log("speech", url);
    audioRef.current.src = url;
  };

  /**
   * @description: 暂停所有的对话播放
   * @return {*}
   */
  const stopAllVoicePlay = () => {
    audioRef.current?.pause();
    let cQlist = batchUpdateChatSomeSpeakingFalse();
    console.log('stopAllVoicePlay cQlist', cQlist);
    setQList([...cQlist])
  };

  /**
   * @description: 播放
   * @param {IQItem} contentItem 对话项
   * @return {*}
   */
  const toPlay = (contentItem: IQItem) => {
    if (isLoading || overlayVisible) {
      return loadingActionFb();
    }
    audioRef?.current?.pause();

    let cQlist = batchUpdateChatSomeSpeakingFalse();
    let currentSpeakingType = contentItem?.speaking ?? false;
    console.log('cQlist', cQlist);

    if (currentSpeakingType) {
      // 已经在播放=> 暂停播放
      console.log('已经在播放需要=> 暂停播放');
      updateChatSome(contentItem?.id, {
        speaking: false
      }, cQlist)
      audioRef?.current?.pause();

      return;
    } else {
      updateChatSome(contentItem?.id, {
        speaking: true
      }, cQlist)
    }

    const content = getChatItemContent(contentItem);
    setOverlayVisible(true);
    plugin.textToSpeech({
      // TODO: 这边需要根据系统设置来设置
      lang: "zh_CN",
      tts: true,
      content: content,
      success: function (res) {
        speech(res.filename);
      },
      fail: function (res) {
        setOverlayVisible(false);
        console.log("fail tts", res);
      },
    });

  };

  const toFeedback = async (params: IToFeedBackParams) => {
    const { id, feedback } = params;
    const dto = {
      ...params,
      "sessionId": selectTalk.id,
    }
    const res = await handleFeedback(dto);
    console.log('toFeedback res', res);
    const { success, msg } = res;
    if (success) {
      Notify.show({
        type: 'success',
        msg: '评价成功',
        duration: 1000,
      })
      updateChatSome(id, {
        feedback
      })
    } else {
      Notify.show({
        type: 'danger',
        msg: msg,
        duration: 3000,
      })
    }
  };

  /**
   * @description: 新建空对话
   * @return {*}
   */
  const createGroup = async () => {
    clearChatContext();
    const recommendContent = await queryRecommended();
    let cList = getDefaultQuestionList(recommendContent)
    setQList(cList);
  }

  /**
   * @description: 生成新的对话上下文
   * @param {*} cValue 问题
   * @param {*} sessionId sessionId
   * @return {*}
   */
  const addHistoryGroup = async (cValue, sessionId) => {
    console.log('addHistoryGroup', selectTalk);
    if (!selectTalk.content) {
      const chatItem = {
        id: sessionId,
        content: cValue
      }
      // 把当前问题作为对话 加入 对话历史
      selectChatHistoryItem(chatItem);
      addChatHistory(chatItem);
    }
  }

  const queryRecommended = async () => {
    let userInfo = Taro.getStorageSync("userInfo");
    const dto = {
      "deviceType": DEVICE_TYPE.app,
      num: DEFAULT_RECOMMENDED_NUMBER,
      userId: userInfo?.username ?? ''
    }
    const res = await getRecommended(dto);
    const { success, msg, content = [] } = res;

    if (!success) {
      Notify.show({
        type: 'danger',
        msg: msg,
        duration: 3000,
      })
    }
    return content;
  }

  /**
   * @description: 更新会话标题
   * @param {any} task 会话
   * @return {*}
   */
  const updateTask = (task: any) => {
    // 如果更新的是当前选中的, 同步也要更新
    if (selectTalk.id === task.id) {
      setSelectTalk(task);
      selectChatHistoryItem(task)
    }

    // 更新历史记录
    let newHistoryList = talkLists.map((item: IChatHistoryItem) => {
      if (item.id == task.id) {
        return (item = { ...item, ...task });
      } else {
        return item;
      }
    });
    setTalkLists(newHistoryList);
    setChatHistory(newHistoryList);
  }

  /**
   * @description: 获取当前登录用户的会话列表
   * @return {*}
   */
  const getUserSessions = async () => {
    setIsLoading(true);
    const res = await fetchSessions();
    setIsLoading(false);
    const { success, msg, content = [] } = res;
    if (success) {
      const { list = [] } = content;
      // 获取到的内容需要过滤, 目前只支持 text_chat
      let filterSessions = list.filter(item => item.type === 'text_chat').filter(item => item.summary).map((item) => {
        return {
          ...item,
          content: item.summary,
        }
      });
      console.log('filterSessions', filterSessions);
      // 设置会话列表
      setChatHistory(filterSessions);
      // 设置当前会话
      let currentTalk = Taro.getStorageSync("chatContext");
      let filterSessionsTalkIds = filterSessions.map((item) => item.id);
      if (!filterSessionsTalkIds.includes(currentTalk.id)) {
        // 当前会话不在列表中, 说明是新的会话, 更新当前会话
        selectChatHistoryItem(filterSessions?.[0] ?? {});
      }
    } else {
      Toast.show("chat-toast", { title: msg })
    }
  }

  /**
   * @description: 根据当前选中的会话加载对话历史: 只有首次进入该页面的时候才会加载
   * @return {*}
   */
  useEffect(() => {
    if (isInitLoadSession) {
      if (selectTalk.id && selectTalk.content) {
        // 加载历史对话
        getSessionMessages(selectTalk.id, true);
      }
    }
  }, [selectTalk, isInitLoadSession])

  /**
   * @description: 监听 Storage 中 会话历史的变化,修改当前state
   * @return {*}
   */
  useDeepCompareEffect(() => {
    let talkLists = Taro.getStorageSync("chatHistory") ?? [];
    console.log('talkLists', talkLists);
    setTalkLists(talkLists || []);
  }, [Taro.getStorageSync("chatHistory")]);

  /**
   * @description: 监听 Storage 中 当前对话的变化,修改当前state
   * @return {*}
   */
  useDeepCompareEffect(() => {
    let token = Taro.getStorageSync("token");

    if (token) {
      let cTalk = Taro.getStorageSync("chatContext") ?? {};
      console.log('talkLists cTalk', cTalk);

      setSelectTalk(cTalk);
    }
  }, [Taro.getStorageSync("chatContext"), Taro.getStorageSync("token")]);

  /**
   * @description: 监听 Storage 中 当前对话的变化,修改当前state
   * @return {*}
   */
  useDeepCompareEffect(() => {
    let token = Taro.getStorageSync("token");
    if (token) {
      getUserSessions();
    }
  }, [Taro.getStorageSync("token")]);


  return {
    isLoading,
    qList,
    value,
    viewContent,
    talkLists,
    selectTalk,
    overlayVisible,
    setIsLoading,
    setQList,
    setValue,
    handle,
    reLoad,
    toPlay,
    cancelAsk,
    toFeedback,
    setViewContent,
    createGroup,
    onHandleChangeTalk,
    updateTask,
  }
  // return (
  //   <ChatContext.Provider
  //     value={{
  //       isLoading,
  //       qList,
  //       value,
  //       viewContent,
  //       talkLists,
  //       selectTalk,
  //       setIsLoading,
  //       setQList,
  //       setValue,
  //       handle,
  //       reLoad,
  //       toPlay,
  //       cancelAsk,
  //       toFeedback,
  //       setViewContent,
  //       createGroup,
  //       onHandleChangeTalk,
  //     }}
  //   >
  //     <>
  //       <BaseOverlay visible={overlayVisible} text="语音合成中。。。" />
  //       <Toast id="chat-toast" />
  //       {children}
  //     </>
  //   </ChatContext.Provider>
  // );
}

// export const ChatContextProvider = ({ children }: IProps): JSX.Element => {

// };

interface IIndexContext {
  showBack: boolean;
  scrollIntoView: string;
  onScrollToLower: () => void;
  scrollToBottom: () => void;
}

// 聊天窗口底部固定 滚动位置
export const CHAT_SCROLL_VIEW_BOTTOM_FIXATION =
  "chat-scroll-view-bottom-fixation";
export const IndexContext = React.createContext<IIndexContext>(
  {} as IIndexContext
);
